﻿using System;
using System.Collections.Generic;
using System.Text;

namespace WorkNodeFramework
{
    public class WorkPart:FieldSet<WorkPart>
    {
        #region Properties
        public string WorkNode {
            get {
                return (string)base["WorkNode"];
            }
            set {
                base["WorkNode"] = value;
            }
        }

        public string Properties {
            get {
                string props = (string)base["Properties"];
                if (string.IsNullOrEmpty(props))
                    return WorkNode + "Properties";
                return props;
            }
            set {
                base["Properties"] = value;
            }
        }

        public string LangProperties
        {
            get
            {
                string props = (string)base["LangProperties"];
                if (string.IsNullOrEmpty(props))
                    return WorkNode + "LangProperties";
                return props;
            }
            set
            {
                base["LangProperties"] = value;
            }
        }

        public string Path {
            get {
                return (string)base["Path"];
            }
            set {
                base["Path"] = value;
            }
        }

        public DateTime FromDate {
            get {
                return (DateTime)base["FromDate"];
            }
            set {
                base["FromDate"] = value;
            }
        }

        public DateTime ToDate {
            get {
                return (DateTime)base["ToDate"];
            }
            set {
                base["ToDate"] = value;
            }
        }

        public bool IsMain {
            get {
                object o = base["IsMain"];
                if (o == null)
                    return false;
                return (bool)o;
            }
            set {
                base["IsMain"] = value;
            }
        }

        public int MaxRow {
            get {
                object o = base["MaxRow"];
                if (o == null)
                    return -1;
                return (int)o;
            }
            set {
                base["MaxRow"] = value;
            }
        }

        DataProvider provider;
        public DataProvider Provider
        {
            get
            {
                if (provider == null)
                    return DataService.Provider;
                return provider;
            }
            internal set
            {
                provider = value;
            }
        }
        #endregion

        #region Get Nodes
        public T GetNode<T>(string wnPath, string name) 
            where T:WorkNode
        {
            if (IsNew)
                return null;
            wnPath = WorkNodeUtility.TrimsPath(wnPath);
            WorkNodeTable wnTable = Provider.GetTable<WorkNodeTable>(WorkNode);
            Select sel = new Select(Provider)
                .From(wnTable)
                .Where
                (
                    WHERE.DO(wnTable.PathColumn).IsEqualsTo(wnPath),
                    WHERE.DO(wnTable.NameColumn).IsEqualsTo(name)
                );
            T node = sel.ExecuteEntity<T>();
            if (node != null) {
                node.FullPath = Path;
                node.WorkPart = this;
            }
            return node;
        }

        public NodeReader<T> GetNodes<T>(string path, params Predicate<T>[] predicates)
           where T : WorkNode
        {
            return GetNodes<T>(path, null, predicates);
        }

        public NodeReader<T> GetNodes<T>(string path, DateSetting dateSetting, params Predicate<T>[] predicates)
           where T : WorkNode
        {
            int top = int.MaxValue;
            return GetNodes<T>(path, top, dateSetting, predicates);
        }

        public NodeReader<T> GetNodes<T>(string path, int top, params Predicate<T>[] predicates)
           where T : WorkNode
        {
            return GetNodes<T>(path, top, null, predicates);
        }

        public NodeReader<T> GetNodes<T>(string path, int top, DateSetting dateSetting, params Predicate<T>[] predicates)
           where T : WorkNode
        {
            return GetNodes<T>(path, 0, top, dateSetting, predicates);
        }

        public NodeReader<T> GetNodes<T>(string path, int pageIndex, int pageCount, params Predicate<T>[] predicates)
            where T : WorkNode
        {
            return GetNodes<T>(path, pageIndex, pageCount, null, predicates);
        }

        public NodeReader<T> GetNodes<T>(string wnPath, int pageIndex, int pageCount, DateSetting dateSetting, params Predicate<T>[] predicates)
            where T : WorkNode
        {
            wnPath = WorkNodeUtility.TrimsPath(wnPath);
            //=========================
            WorkNodeTable wnTable = Provider.GetTable<WorkNodeTable>(WorkNode);
            WorkNodeTypeAttribute wnType = WorkNodeUtility.GetWorkNodeType<T>();
            //=========================
            Select select = GetSelect(wnTable, wnPath, wnType.Name);
            //=========================
            if (dateSetting != null)
            {
                Column dateCol = dateSetting.DateType == DateType.OpenDate ? wnTable.OpenDateColumn : wnTable.LastDateColumn;
                dateSetting.UpdateSelectQuery(select, dateCol);
            }
            return new NodeReader<T>(select, this, pageIndex, pageCount, predicates);
        }

        Select GetSelect(WorkNodeTable wnTable, string wnPath, string wnName)
        {
            Select select = new Select(wnTable.Provider)
                .From(wnTable)
                .Where
                (
                    WHERE.DO(wnTable.PathColumn).IsEqualsTo(wnPath),
                    WHERE.DO(wnTable.TypeColumn).IsEqualsTo(wnName)
                );

            return select;
        }
        #endregion

        #region Execute Nodes
        internal void UpdateNode<T>(T node)
            where T : WorkNode
        {
            Provider.ExecuteTransaction(GetUpdateCommandManager(node));
        }

        internal void InsertNode<T>(string wnPath,T node)
            where T : WorkNode
        {
            Provider.ExecuteTransaction(GetInsertCommandManager(wnPath, node));
        }

        internal void DeleteNodes<T>(params T[] nodes)
            where T : WorkNode
        {
            Provider.ExecuteTransaction(GetDeleteCommandManager(nodes));
        }
        #endregion

        #region Commands
        internal ExecuteCommandManager GetInsertCommandManager<T>(string wnPath, T node)
            where T : WorkNode
        {
            WorkNodeTable wnTable = Provider.GetTable<WorkNodeTable>(WorkNode);
            WorkNodeTypeAttribute wnType = WorkNodeUtility.GetWorkNodeType<T>();
            if (string.IsNullOrEmpty(node.Name))
            {
                if (wnType.NameRequired)
                    throw new WorkNodeNameEmptyException("The name of WorkNode can not be null or empty.");
                else
                    node.Name = string.Empty;
            }
            string path = wnPath;
            if (string.IsNullOrEmpty(wnPath))
                path = string.Empty;
            if (!wnType.AllowDuplicateName)
            {
                int count = new Select(Provider)
                    .From(wnTable)
                    .Where
                    (
                        WHERE.DO(wnTable.NameColumn).IsEqualsTo(node.Name),
                        WHERE.DO(wnTable.TypeColumn).IsEqualsTo(wnType.Name),
                        WHERE.DO(wnTable.PathColumn).IsEqualsTo(path)
                    )
                    .Count();
                if (count > 0)
                    throw new WorkNodeDuplicateNameException("This name has been used.");
            }
            Guid id = Guid.NewGuid();
            node.ID = id;
            node.Path = path;
            node.ParentID = null;
            node.Attributes = 0;
            node.WorkNodeType = wnType.Name;
            node.OpenDate = DateTime.Now;
            node.WorkPart = this;
            //=========================
            StringBuilder sb = new StringBuilder();
            ParameterIndexCollection pars = new ParameterIndexCollection();
            string wnCmdText = node.BuildSaveStatement(wnTable, pars);
            sb.Append(wnCmdText + Environment.NewLine);

            foreach (WorkNodeProperties p in node.Properties)
            {
                p.WorkNodeID = id;
                p.OpenDate = DateTime.Now;
                string pCmdText = p.BuildSaveStatement(node.Properties.Table, pars);
                if (!string.IsNullOrEmpty(pCmdText))
                    sb.Append(pCmdText + Environment.NewLine);
            }
            foreach (WorkNodeLangProperties lp in node.LangProperties)
            {
                lp.WorkNodeID = id;
                lp.OpenDate = DateTime.Now;
                string lpCmdText = lp.BuildSaveStatement(node.LangProperties.Table, pars);
                if (!string.IsNullOrEmpty(lpCmdText))
                    sb.Append(lpCmdText + Environment.NewLine);
            }

            string cmdText = sb.ToString();
            ExecuteCommand cmd = new ExecuteCommand(cmdText, pars.ToDbParameters(Provider));
            ExecuteCommandManager cm = new ExecuteCommandManager(cmd);
            cm.ExecuteCommandsCompleted += delegate(object sender, EventArgs e)
            {
                foreach (WorkNodeProperties p in node.Properties)
                    ((IFieldSet)p).DefaultStatus = FieldSetStatus.Ready;
                foreach (WorkNodeLangProperties lp in node.LangProperties)
                    ((IFieldSet)lp).DefaultStatus = FieldSetStatus.Ready;
                node.FullPath = path;
                ((IFieldSet)node).DefaultStatus = FieldSetStatus.Ready;
            };

            return cm;
        }

        internal ExecuteCommandManager GetUpdateCommandManager<T>(T node)
            where T : WorkNode
        {
            WorkNodeTable wnTable = Provider.GetTable<WorkNodeTable>(WorkNode);
            WorkNodeTypeAttribute wnType = WorkNodeUtility.GetWorkNodeType<T>();
            if (string.IsNullOrEmpty(node.Name))
            {
                if (wnType.NameRequired)
                    throw new WorkNodeNameEmptyException("The name of WorkNode can not be null or empty.");
                else
                    node.Name = "";
            }

            if (!wnType.AllowDuplicateName)
            {
                int count = new Select(Provider)
                    .From(wnTable)
                    .Where
                    (
                        WHERE.DO(wnTable.NameColumn).IsEqualsTo(node.Name),
                        WHERE.DO(wnTable.TypeColumn).IsEqualsTo(wnType.Name),
                        WHERE.DO(wnTable.PathColumn).IsEqualsTo(node.Path),
                        WHERE.DO(wnTable.IDColumn).IsNotEquals(node.ID)
                    )
                    .Count();
                if (count > 0)
                    throw new WorkNodeDuplicateNameException("This name has been used.");
            }
            //=========================
            node.LastDate = DateTime.Now;
            //=========================
            StringBuilder sb = new StringBuilder();
            ParameterIndexCollection pars = new ParameterIndexCollection();
            string wnCmdText = node.BuildSaveStatement(wnTable, pars);
            sb.Append(wnCmdText + Environment.NewLine);

            foreach (WorkNodeProperties p in node.Properties)
            {
                if (p.IsNew)
                    p.OpenDate = DateTime.Now;
                else if (p.IsModified)
                    p.LastDate = DateTime.Now;
                string pCmdText = p.BuildSaveStatement(node.Properties.Table, pars);
                if (!string.IsNullOrEmpty(pCmdText))
                    sb.Append(pCmdText + Environment.NewLine);
            }
            foreach (WorkNodeLangProperties lp in node.LangProperties)
            {
                if (lp.IsNew)
                    lp.OpenDate = DateTime.Now;
                else if (lp.IsModified)
                    lp.LastDate = DateTime.Now;
                string lpCmdText = lp.BuildSaveStatement(node.LangProperties.Table, pars);
                if (!string.IsNullOrEmpty(lpCmdText))
                    sb.Append(lpCmdText + Environment.NewLine);
            }

            string cmdText = sb.ToString();
            ExecuteCommand cmd = new ExecuteCommand(cmdText, pars.ToDbParameters(Provider));
            ExecuteCommandManager cm = new ExecuteCommandManager(cmd);
            cm.ExecuteCommandsCompleted += delegate(object sender, EventArgs e) {
                foreach (WorkNodeProperties p in node.Properties)
                    ((IFieldSet)p).DefaultStatus = FieldSetStatus.Ready;
                foreach (WorkNodeLangProperties lp in node.LangProperties)
                    ((IFieldSet)lp).DefaultStatus = FieldSetStatus.Ready;
                ((IFieldSet)node).DefaultStatus = FieldSetStatus.Ready;
            };
            return cm;
        }

        internal ExecuteCommandManager GetDeleteCommandManager<T>(params T[] nodes)
            where T : WorkNode
        {
            WorkNodeTable wnTable = Provider.GetTable<WorkNodeTable>(WorkNode);
            WorkNodePropertiesTable wnpTable = Provider.GetTable<WorkNodePropertiesTable>(Properties);
            WorkNodeLangPropertiesTable wnlpTable = Provider.GetTable<WorkNodeLangPropertiesTable>(LangProperties);
            List<IExecuteCommand> commandList = new List<IExecuteCommand>();
            foreach (T node in nodes)
            {
                Delete delProps = new Delete(Provider)
                    .From(wnpTable)
                    .Where(WHERE.DO(wnpTable.WorkNodeIDColumn).IsEqualsTo(node.ID));
                Delete delLProps = new Delete(Provider)
                    .From(wnlpTable)
                    .Where(WHERE.DO(wnlpTable.WorkNodeIDColumn).IsEqualsTo(node.ID));
                Delete delNode = new Delete(Provider)
                    .From(wnTable)
                    .Where(WHERE.DO(wnTable.IDColumn).IsEqualsTo(node.ID));
                commandList.Add(delProps);
                commandList.Add(delLProps);
                commandList.Add(delNode);
            }
            ExecuteCommand[] cmds = new ExecuteCommand[commandList.Count];
            for (int i = 0; i < commandList.Count; i++)
                cmds[i] = commandList[i].BuildExecuteCommand();
            ExecuteCommandManager cm = new ExecuteCommandManager(cmds);
            return cm;
        }
        #endregion

        public override string ToString()
        {
            if (!string.IsNullOrEmpty(WorkNode))
                return WorkNode;
            return base.ToString();
        }
    }
}
